// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Instrument.SecurityChecker.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge
{
    #region Usings

    using System;
    using System.Reflection;

    using Org.OpenTrader.Framework.Enums;
    using Org.OpenTrader.Framework.Forge.Enums;
    using Org.OpenTrader.Framework.Forge.Interfaces;

    #endregion

    /// <summary>
    /// The instrument.
    /// </summary>
    public partial class Instrument
    {
        /// <summary>
        /// The security checker.
        /// </summary>
        internal class SecurityChecker : IDisposable
        {
            #region Constants and Fields

            /// <summary>
            /// The logging path.
            /// </summary>
            private static readonly Identity Identity = Identity.Create(MethodBase.GetCurrentMethod());

            #endregion

            #region Public Methods

            /// <summary>
            /// Check Cancel Request
            /// </summary>
            /// <param name="i">
            /// </param>
            /// <param name="order">
            /// </param>
            /// <returns>
            /// The check order cancel request.
            /// </returns>
            public static bool CheckOrderCancelRequest(IInstrument i, IOrder order)
            {
                // Check the instrument : Unknown
                if (i == null)
                {
                    // CheckOrderCancelRequest: Cannot allow send request, Unknown Instrument
                    Logging.Log(Identity, ELogLevel.Error, () => string.Format("CheckOrderCancelRequest : Cannot allow send request, Unknown Instrument"));
                    return false;
                }

                // Check the order : null
                if (order == null)
                {
                    // CheckOrderCancelRequest: Cannot allow send request, Unknown Order
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderCancelRequest : Cannot allow send request, Unknown Order : {0} ({1})", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope));
                    return false;
                }

                // CheckOrderCancelRequest: Allowing send request
                Logging.Log(
                    Identity, 
                    ELogLevel.Info, 
                    () =>
                    string.Format(
                        "CheckOrderCancelRequest : Allowing send request : {0} ({1}) : Order ID = {2}", 
                        i.InstrumentStaticData.Code, 
                        i.InstrumentStaticData.TradingScope, 
                        order.ID));
                return true;
            }

            /// <summary>
            /// Check Modify request
            /// </summary>
            /// <param name="i">
            /// </param>
            /// <param name="oldOrder">
            /// </param>
            /// <param name="newOrder">
            /// </param>
            /// <returns>
            /// The check order modify request.
            /// </returns>
            public static bool CheckOrderModifyRequest(IInstrument i, IOrder oldOrder, IOrder newOrder)
            {
                // Check the instrument : Unknown
                if (i == null)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, Unknown Instrument
                    Logging.Log(Identity, ELogLevel.Error, () => string.Format("CheckOrderModifyRequest : Cannot allow send request, Unknown Instrument"));
                    return false;
                }

                if (i.InstrumentStaticData == null)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, Unknown Instrument Static Data
                    Logging.Log(
                        Identity, ELogLevel.Error, () => string.Format("CheckOrderModifyRequest : Cannot allow send request, Unknown Instrument Static Data"));
                    return false;
                }

                if (! i.InstrumentStaticData.FactorPrice.HasValue)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, Invalid Factor Price
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderModifyRequest : Cannot allow send request, No Factor Price (check PointValue and/or CurrencyPrice) : {0} ({1})", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope));
                    return false;
                }

                if (i.InstrumentStaticData.FactorPrice < Constants.Epsilon)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, Invalid Factor Price
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderModifyRequest : Cannot allow send request, Invalid Factor Price : {0} ({1})", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope));
                    return false;
                }

                // Check the old order : null
                if (oldOrder == null)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, Unknown Old Order
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderModifyRequest : Cannot allow send request, Unknown Old Order : {0} ({1})", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope));
                    return false;
                }

                // Check the vew order : null
                if (newOrder == null)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, Unknown New Order
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderModifyRequest : Cannot allow send request, Unknown New Order : {0} ({1})", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope));
                    return false;
                }

                // Check the old order : invalid old order
                if (!oldOrder.IsValid)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, invalid old order
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderModifyRequest : Cannot allow send request, Invalid Old Order : {0} ({1}) : Order ID = {2}", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope, 
                            oldOrder.ID));
                    return false;
                }

                // Check the new order : invalid new order
                if (!newOrder.IsValid)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, invalid new order
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderModifyRequest : Cannot allow send request, Invalid New Order : {0} ({1}) : Order ID = {2}", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope, 
                            newOrder.ID));
                    return false;
                }

                // Check Nominal Trade
                var nominaltrade = newOrder.Quantity * newOrder.Price * i.InstrumentStaticData.FactorPrice;
                if (nominaltrade > i.InstrumentStaticData.MaxNominalTrade)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, nominal breaks allowed trade limit
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderModifyRequest : Cannot allow send request, nominal breaks allowed trade limit : {0} ({1}) : Requested Nominal = {2} and MaxNominalTrade = {3}", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope, 
                            nominaltrade, 
                            i.InstrumentStaticData.MaxNominalTrade));
                    return false;
                }

                // sign order nominal
                if (newOrder.Way == EOrderWay.Sell)
                {
                    nominaltrade = -nominaltrade;
                }

                // Check the expo
                var texpo = i.Expo[EExpoCellType.Traded];
                var iexpo = i.Expo[EExpoCellType.Inventory];
                var wexpo = i.Expo[EExpoCellType.Working];
                var pexpo = i.Expo[EExpoCellType.Pending];
                if (texpo == null || iexpo == null || wexpo == null || pexpo == null)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, Invalid Expo
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderModifyRequest : Cannot allow send request, Invalid Expo : {0} ({1})", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope));
                    return false;
                }

                // Check MaxNominalBook
                var target = ((texpo.NominalExpo + iexpo.NominalExpo + wexpo.NominalExpo + pexpo.NominalExpo) * i.InstrumentStaticData.FactorPrice) +
                             nominaltrade;
                if (! target.HasValue || Math.Abs(target.Value) > i.InstrumentStaticData.MaxNominalBook)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, MaxNominalBook
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderModifyRequest : Cannot allow send request, nominal book breaks allowed nominal book limit : {0} ({1}) : Target Book = {2} and MaxNominalBook = {3}", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope, 
                            target, 
                            i.InstrumentStaticData.MaxNominalBook));
                    return false;
                }

                // Check Short Sell
                var orderqty = 0;
                if (newOrder.Way == EOrderWay.Buy)
                {
                    orderqty = newOrder.Quantity;
                }
                else if (newOrder.Way == EOrderWay.Sell)
                {
                    orderqty = -newOrder.Quantity;
                }

                var targetqty = texpo.Inventory + iexpo.Inventory + wexpo.Inventory + pexpo.Inventory + orderqty;
                if (targetqty < 0 && !i.InstrumentStaticData.ShortSellEnabled)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, Short Sell not allowed
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderModifyRequest : Cannot allow send request, Short Sell not allowed : {0} ({1}) : Target Book = {2}", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope, 
                            targetqty));
                    return false;
                }

                // Check Max deviation
                var refprice = 0.0;
                if (newOrder.Way == EOrderWay.Buy)
                {
                    if (i.MarketFields[EMarketField.BestBid].HasValue)
                    {
                        refprice = i.MarketFields[EMarketField.BestBid];
                    }
                }
                else if (newOrder.Way == EOrderWay.Sell)
                {
                    if (i.MarketFields[EMarketField.BestAsk].HasValue)
                    {
                        refprice = i.MarketFields[EMarketField.BestAsk];
                    }
                }

                var dev = (refprice < Constants.Zero) ? 1.0 : (newOrder.Price - refprice) / refprice;
                if (Math.Abs(dev) >= i.InstrumentStaticData.MaxVariationPercentage)
                {
                    // CheckOrderModifyRequest: Cannot allow send request, Max Deviation
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderModifyRequest : Cannot allow send request, Max Deviation : {0} ({1}) : Deviation = {2} and MaxDeviation = {3}", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope, 
                            dev, 
                            i.InstrumentStaticData.MaxVariationPercentage));
                    return false;
                }

                // CheckOrderModifyRequest: Allowing send request
                Logging.Log(
                    Identity, 
                    ELogLevel.Info, 
                    () =>
                    string.Format(
                        "CheckOrderModifyRequest : Allowing send request : {0} ({1}) : {2} @ {3}", 
                        i.InstrumentStaticData.Code, 
                        i.InstrumentStaticData.TradingScope, 
                        orderqty, 
                        newOrder.Price));
                return true;
            }

            /// <summary>
            /// Check Send request
            /// </summary>
            /// <param name="i">
            /// </param>
            /// <param name="order">
            /// </param>
            /// <returns>
            /// The check order send request.
            /// </returns>
            public static bool CheckOrderSendRequest(IInstrument i, IOrder order)
            {
                // Check the instrument : Unknown
                if (i == null)
                {
                    // CheckOrderCancelRequest: Cannot allow send request, Unknown Instrument
                    Logging.Log(Identity, ELogLevel.Error, () => string.Format("CheckOrderSendRequest: Cannot allow send request, Unknown Instrument"));
                    return false;
                }

                if (i.InstrumentStaticData == null)
                {
                    // CheckOrderCancelRequest: Cannot allow send request, Unknown Instrument Static Data
                    Logging.Log(
                        Identity, ELogLevel.Error, () => string.Format("CheckOrderSendRequest: Cannot allow send request, Unknown Instrument Static Data"));
                    return false;
                }

                if (!i.InstrumentStaticData.FactorPrice.HasValue)
                {
                    // CheckOrderCancelRequest: Cannot allow send request, Invalid Factor Price
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderSendRequest: Cannot allow send request, Invalid Factor Price : {0} ({1})", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope));
                    return false;
                }

                if (i.InstrumentStaticData.FactorPrice < Constants.Epsilon)
                {
                    // CheckOrderCancelRequest: Cannot allow send request, Invalid Factor Price
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderSendRequest: Cannot allow send request, Invalid Factor Price : {0} ({1})", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope));
                    return false;
                }

                // Check the order : null
                if (order == null)
                {
                    // CheckOrderCancelRequest: Cannot allow send request, Unknown Order
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderSendRequest: Cannot allow send request, Unknown Order : {0} ({1})", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope));
                    return false;
                }

                // Check the order : invalid order
                if (!order.IsValid)
                {
                    // CheckOrderSendRequest: Cannot allow send request, invalid order ID
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderSendRequest: Cannot allow send request, Invalid Order : {0} ({1}) : Order ID = {2}", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope, 
                            order.ID));
                    return false;
                }

                // Check Nominal Trade
                var nominaltrade = order.Quantity * order.Price * i.InstrumentStaticData.FactorPrice;
                if (nominaltrade > i.InstrumentStaticData.MaxNominalTrade)
                {
                    // CheckOrderSendRequest: Cannot allow send request, nominal breaks allowed trade limit
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderSendRequest: Cannot allow send request, nominal breaks allowed trade limit : {0} ({1}) : Requested Nominal = {2} and MaxNominalTrade = {3}", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope, 
                            nominaltrade, 
                            i.InstrumentStaticData.MaxNominalTrade));
                    return false;
                }

                // sign order nominal
                if (order.Way == EOrderWay.Sell)
                {
                    nominaltrade = -nominaltrade;
                }

                // Check the expo
                var texpo = i.Expo[EExpoCellType.Traded];
                var iexpo = i.Expo[EExpoCellType.Inventory];
                var wexpo = i.Expo[EExpoCellType.Working];
                var pexpo = i.Expo[EExpoCellType.Pending];
                if (texpo == null || iexpo == null || wexpo == null || pexpo == null)
                {
                    // CheckOrderSendRequest: Cannot allow send request, Invalid Expo
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderSendRequest: Cannot allow send request, Invalid Expo : {0} ({1})", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope));
                    return false;
                }

                // Check MaxNominalBook
                var target = ((texpo.NominalExpo + iexpo.NominalExpo + wexpo.NominalExpo + pexpo.NominalExpo) * i.InstrumentStaticData.FactorPrice) +
                             nominaltrade;
                if (! target.HasValue || Math.Abs(target.Value) > i.InstrumentStaticData.MaxNominalBook)
                {
                    // CheckOrderSendRequest: Cannot allow send request, MaxNominalBook
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderSendRequest: Cannot allow send request, nominal book breaks allowed nominal book limit : {0} ({1}) : Target Book = {2} and MaxNominalBook = {3}", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope, 
                            target, 
                            i.InstrumentStaticData.MaxNominalBook));
                    return false;
                }

                // Check Short Sell
                var orderqty = 0;
                if (order.Way == EOrderWay.Buy)
                {
                    orderqty = order.Quantity;
                }
                else if (order.Way == EOrderWay.Sell)
                {
                    orderqty = -order.Quantity;
                }

                var targetqty = texpo.Inventory + iexpo.Inventory + wexpo.Inventory + pexpo.Inventory + orderqty;
                if (targetqty < 0 && !i.InstrumentStaticData.ShortSellEnabled)
                {
                    // CheckOrderSendRequest: Cannot allow send request, Short Sell not allowed
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderSendRequest: Cannot allow send request, Short Sell not allowed : {0} ({1}) : Target Book = {2}", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope, 
                            targetqty));
                    return false;
                }

                // Check Max deviation
                var refprice = 0.0;
                if (order.Way == EOrderWay.Buy)
                {
                    if (i.MarketFields[EMarketField.BestBid].HasValue)
                    {
                        refprice = i.MarketFields[EMarketField.BestBid];
                    }
                }
                else if (order.Way == EOrderWay.Sell)
                {
                    if (i.MarketFields[EMarketField.BestAsk].HasValue)
                    {
                        refprice = i.MarketFields[EMarketField.BestAsk];
                    }
                }

                var dev = (refprice < Constants.Zero) ? 1.0 : (order.Price - refprice) / refprice;
                if (Math.Abs(dev) >= i.InstrumentStaticData.MaxVariationPercentage)
                {
                    // CheckOrderSendRequest: Cannot allow send request, Max Deviation
                    Logging.Log(
                        Identity, 
                        ELogLevel.Error, 
                        () =>
                        string.Format(
                            "CheckOrderSendRequest: Cannot allow send request, Max Deviation : {0} ({1}) : Deviation = {2} and MaxDeviation = {3}", 
                            i.InstrumentStaticData.Code, 
                            i.InstrumentStaticData.TradingScope, 
                            dev, 
                            i.InstrumentStaticData.MaxVariationPercentage));
                    return false;
                }

                // CheckOrderCancelRequest: Allowing send request
                Logging.Log(
                    Identity, 
                    ELogLevel.Info, 
                    () =>
                    string.Format(
                        "CheckOrderSendRequest: Allowing send request : {0} ({1}) : {2} @ {3}", 
                        i.InstrumentStaticData.Code, 
                        i.InstrumentStaticData.TradingScope, 
                        orderqty, 
                        order.Price));
                return true;
            }

            #endregion

            #region Implemented Interfaces

            #region IDisposable

            /// <summary>
            /// The dispose.
            /// </summary>
            public void Dispose()
            {
            }

            #endregion

            #endregion
        }
    }
}